<template>
  <Form
    v-bind="otherProps"
    :disabled="disabled"
    :model="_model"
    ref="formRef"
    @finishFailed="(error) => validateFn(error)"
  >
    <slot name="customRender">
      <component :is="defaultComponent()" />
    </slot>
  </Form>
</template>

<script setup lang="tsx">
import { toReactive } from '@vueuse/core';
import type { BoFormProps, Column, TabPaneProps } from './interface';
import { Form, FormInstance, Row, Col, Tabs, TabPane } from 'ant-design-vue';
import { computed, VNode, watch, ref, provide } from 'vue';
import BoFormItem from './BoFormItem.vue';
import { deepDiff } from '@boeyo/shared';
import { useProvideFormInstance } from './formInstance';
import { NamePath } from 'ant-design-vue/es/form/interface';
import { cloneDeep } from 'lodash-es';

const {
  model,
  columns,
  tabs,
  rowProps,
  colProps = {
    xs: 24,
    sm: 24,
    md: 24,
    lg: 24,
  },
  onValuesChange,
  disabled,
  ...otherProps
} = defineProps<BoFormProps>();

const slots = defineSlots();

const _model = toReactive(model || {});
let oldModel: Record<string, any> = cloneDeep(_model);
watch(
  _model,
  () => {
    if (onValuesChange) {
      const result = deepDiff(oldModel, _model);
      onValuesChange?.(result, _model);
    }
    oldModel = cloneDeep(_model);
  },
  { deep: true },
);

const formRef = ref<FormInstance>();
useProvideFormInstance(formRef.value!);
const validateFn = (error: any) => {
  if (!tabs?.length) return;
  const key = error.errorFields[0].name[0];
  const parentKey = findParentKey(tabs, 'dataIndex', key);
  onTabChange(parentKey);
};
const columnRender = (col: any) => {
  if (col.formItemRender) {
    return col.formItemRender(_model);
  } else {
    return (
      <BoFormItem
        {...col}
        model={_model}
        v-slots={{ default: () => slots[col.dataIndex]?.({ model: _model }) }}
      ></BoFormItem>
    );
  }
};
const currentIndex = ref(0);
const activeKey = computed<string | undefined>(() => {
  if (!tabs?.length) return undefined;
  return (tabs?.[currentIndex.value] as TabPaneProps)?.key;
});

const onTabChange = (key: any) => {
  const index = tabs?.findIndex((item: any) => item.key === key);
  if (index === currentIndex.value) return;
  currentIndex.value = index!;
};
const findParentKey = (tree: any, fieldName: any, fieldValue: any) => {
  const searchParent: any = (nodes: any, fieldName: any, fieldValue: any) => {
    for (let node of nodes) {
      for (let child of node.columns || []) {
        if (child[fieldName] === fieldValue) {
          return node.key;
        }
        const parentKey = searchParent([child], fieldName, fieldValue);
        if (parentKey) {
          return parentKey;
        }
      }
    }
    return null;
  };

  return searchParent(tree, fieldName, fieldValue);
};

const genColumn = (columns: Column[]) => {
  return (
    <Row gutter={[40, 0]} {...rowProps}>
      {columns?.reduce<VNode[]>((prev, cur) => {
        const { hidden, colProps: _colProps, ...col } = cur;
        if (hidden?.(_model)) {
          return prev;
        }
        prev.push(
          <Col key={col.dataIndex} {...(_colProps || colProps)}>
            {columnRender(col)}
          </Col>,
        );
        return prev;
      }, [])}
    </Row>
  );
};

const defaultComponent = () => {
  return tabs?.length ? (
    <div class='tabs-header'>
      <Tabs
        activeKey={activeKey.value}
        onTabClick={(key) => onTabChange(key as string)}
        type={'card'}
      >
        {tabs?.map((item) => {
          return (
            <TabPane tab={item.tab} key={item.key} forceRender>
              {genColumn(item.columns)}
            </TabPane>
          );
        })}
      </Tabs>
    </div>
  ) : (
    genColumn(columns!)
  );
};

defineExpose({
  clearValidate: (name?: NamePath) => formRef.value?.clearValidate(name),
  resetFields: (name?: NamePath) => formRef.value?.resetFields(name),
  scrollToField: (name: NamePath, options: [ScrollOptions]) =>
    formRef.value?.scrollToField(name, options),
  validate: async (name?: NamePath[]) => {
    try {
      await formRef.value?.validate(name);
      return _model;
    } catch (error: any) {
      validateFn(error);
      throw Error('表单校验未通过！');
    }
  },
  validateFields: async (name?: NamePath[]) => {
    try {
      await formRef.value?.validateFields(name);
      return _model;
    } catch (error: any) {
      validateFn(error);
      throw Error('表单校验未通过！');
    }
  },
  getFieldsValue: () => _model,
  setFiledValue: (key: string, value: any) => {
    _model[key] = value;
  },
  setFieldsValue: (Obj: any) => {
    for (let item in Obj) {
      _model[item] = Obj[item];
    }
  },
});
</script>
